package pl.allegro.tech.hermes.integrationtests;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.not;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static org.apache.hc.core5.http.HttpStatus.SC_OK;
import static org.apache.hc.core5.http.HttpStatus.SC_UNAUTHORIZED;
import static pl.allegro.tech.hermes.api.SubscriptionOAuthPolicy.clientCredentialsGrantOAuthPolicy;
import static pl.allegro.tech.hermes.api.SubscriptionOAuthPolicy.passwordGrantOAuthPolicy;
import static pl.allegro.tech.hermes.test.helper.builder.OAuthProviderBuilder.oAuthProvider;
import static pl.allegro.tech.hermes.test.helper.builder.SubscriptionBuilder.subscription;
import static pl.allegro.tech.hermes.test.helper.builder.TopicBuilder.topicWithRandomName;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import pl.allegro.tech.hermes.api.SubscriptionOAuthPolicy;
import pl.allegro.tech.hermes.api.Topic;
import pl.allegro.tech.hermes.integrationtests.setup.HermesExtension;
import pl.allegro.tech.hermes.integrationtests.subscriber.TestSubscriber;
import pl.allegro.tech.hermes.integrationtests.subscriber.TestSubscribersExtension;
import pl.allegro.tech.hermes.test.helper.oauth.server.OAuthClient;
import pl.allegro.tech.hermes.test.helper.oauth.server.OAuthResourceOwner;
import pl.allegro.tech.hermes.test.helper.oauth.server.OAuthTestServer;

public class OAuthIntegrationTest {

  @RegisterExtension public static final HermesExtension hermes = new HermesExtension();

  @RegisterExtension
  public static final TestSubscribersExtension subscribers = new TestSubscribersExtension();

  private static final OAuthTestServer oAuthTestServer = new OAuthTestServer();

  private static final OAuthClient oAuthClient = new OAuthClient("client1", "secret1");

  private static final OAuthResourceOwner resourceOwner =
      new OAuthResourceOwner("testUser1", "password1");

  private final SubscriptionOAuthPolicy usernamePasswordOAuthPolicy =
      passwordGrantOAuthPolicy("provider1")
          .withUsername(resourceOwner.username())
          .withPassword(resourceOwner.password())
          .build();

  private final SubscriptionOAuthPolicy clientCredentialsOAuthPolicy =
      clientCredentialsGrantOAuthPolicy("provider1").build();

  @BeforeAll
  public static void initialize() {
    oAuthTestServer.start();
    hermes
        .initHelper()
        .createOAuthProvider(
            oAuthProvider("provider1")
                .withTokenEndpoint(oAuthTestServer.getTokenEndpoint())
                .withClientId(oAuthClient.clientId())
                .withClientSecret(oAuthClient.secret())
                .withRequestTimeout(10 * 1000)
                .withTokenRequestInitialDelay(100)
                .withTokenRequestMaxDelay(10000)
                .build());
  }

  @AfterAll
  public static void tearDown() {
    oAuthTestServer.stop();
  }

  @BeforeEach
  public void initializeAlways() {
    oAuthTestServer.reset();
  }

  @Test
  public void shouldSendMessageToUsernamePasswordGrantOAuthSecuredEndpoint() {
    // given
    String token = oAuthTestServer.stubAccessTokenForPasswordGrant(oAuthClient, resourceOwner);
    TestSubscriber subscriber = subscribers.createSubscriber();
    Topic topic = hermes.initHelper().createTopic(topicWithRandomName().build());
    hermes
        .initHelper()
        .createSubscription(
            subscription(topic, "subscription1")
                .withEndpoint(subscriber.getEndpoint())
                .withOAuthPolicy(usernamePasswordOAuthPolicy)
                .build());

    // when
    hermes.api().publishUntilSuccess(topic.getQualifiedName(), "hello world");

    // then
    subscriber.waitUntilMessageWithHeaderReceived("Authorization", "Bearer " + token);
  }

  @Test
  public void
      shouldRequestNewTokenIfPreviousIsInvalidForUsernamePasswordGrantOAuthSecuredEndpoint() {
    // given
    String invalidToken =
        oAuthTestServer.stubAccessTokenForPasswordGrant(oAuthClient, resourceOwner);
    TestSubscriber subscriber =
        subscribers.createSubscriber(
            (service, endpoint) -> {
              service.addStubMapping(
                  (post(endpoint))
                      .withHeader("Authorization", equalTo("Bearer " + invalidToken))
                      .willReturn(aResponse().withStatus(SC_UNAUTHORIZED))
                      .build());

              service.addStubMapping(
                  (post(endpoint))
                      .withHeader("Authorization", not(equalTo("Bearer " + invalidToken)))
                      .willReturn(aResponse().withStatus(SC_OK))
                      .build());
            });
    Topic topic = hermes.initHelper().createTopic(topicWithRandomName().build());
    hermes
        .initHelper()
        .createSubscription(
            subscription(topic, "subscription2")
                .withEndpoint(subscriber.getEndpoint())
                .withOAuthPolicy(usernamePasswordOAuthPolicy)
                .build());

    // when
    hermes.api().publishUntilSuccess(topic.getQualifiedName(), "hello world");

    // then
    subscriber.waitUntilMessageWithHeaderReceived("Authorization", "Bearer " + invalidToken);

    // and when
    String validToken = oAuthTestServer.stubAccessTokenForPasswordGrant(oAuthClient, resourceOwner);

    // then
    subscriber.waitUntilMessageWithHeaderReceived("Authorization", "Bearer " + validToken);
  }

  @Test
  public void shouldSendMessageToClientCredentialsGrantOAuthSecuredEndpoint() {
    // given
    String token = oAuthTestServer.stubAccessTokenForClientCredentials(oAuthClient);
    TestSubscriber subscriber = subscribers.createSubscriber();
    Topic topic = hermes.initHelper().createTopic(topicWithRandomName().build());
    hermes
        .initHelper()
        .createSubscription(
            subscription(topic, "subscription3")
                .withEndpoint(subscriber.getEndpoint())
                .withOAuthPolicy(clientCredentialsOAuthPolicy)
                .build());

    // when
    hermes.api().publishUntilSuccess(topic.getQualifiedName(), "hello world");

    // then
    subscriber.waitUntilMessageWithHeaderReceived("Authorization", "Bearer " + token);
  }

  @Test
  public void shouldRequestNewTokenIfPreviousIsInvalidForClientCredentialsGrantSecuredEndpoint() {
    // given
    String invalidToken = oAuthTestServer.stubAccessTokenForClientCredentials(oAuthClient);
    TestSubscriber subscriber =
        subscribers.createSubscriber(
            (service, endpoint) -> {
              service.addStubMapping(
                  (post(endpoint))
                      .withHeader("Authorization", equalTo("Bearer " + invalidToken))
                      .willReturn(aResponse().withStatus(SC_UNAUTHORIZED))
                      .build());

              service.addStubMapping(
                  (post(endpoint))
                      .withHeader("Authorization", not(equalTo("Bearer " + invalidToken)))
                      .willReturn(aResponse().withStatus(SC_OK))
                      .build());
            });
    Topic topic = hermes.initHelper().createTopic(topicWithRandomName().build());
    hermes
        .initHelper()
        .createSubscription(
            subscription(topic, "subscription4")
                .withEndpoint(subscriber.getEndpoint())
                .withOAuthPolicy(clientCredentialsOAuthPolicy)
                .build());

    // when
    hermes.api().publishUntilSuccess(topic.getQualifiedName(), "hello world");

    // then
    subscriber.waitUntilMessageWithHeaderReceived("Authorization", "Bearer " + invalidToken);

    // and when
    String validToken = oAuthTestServer.stubAccessTokenForClientCredentials(oAuthClient);

    // then
    subscriber.waitUntilMessageWithHeaderReceived("Authorization", "Bearer " + validToken);
  }
}
